package com.globallogic.javatrainings.sergiiivanov.model;

import java.io.Serializable;

import com.globallogic.javatrainings.sergiiivanov.model.Room.RoomType;
import com.globallogic.javatrainings.sergiiivanov.resources.ErrorCodes;

/**
 * Stores information about buildings<br>
 * Contains inner collection MyArray&lt;Room&gt; <br>
 * Location in data structure:<br>
 * {@link University}.{@link BuildingsManager}.<b>Building</b><br>
 * Typical calls:<br>
 * {@link University#buildings university.buildings}.
 * {@link BuildingsManager#getBuildingByIndex(int) getBuildingByIndex(int)}<br>
 * 
 * @author Sergey
 */
public class Building implements Serializable, MyArrayToNamesCovertable {
    private static final long serialVersionUID = 1L;
    private String name = Const.STRING_EMPTY;
    private MyArray<Room> rooms = new MyArray<Room>(new Room[0]);
    private BuildingsManager manager;

    /**
     * Default constructor. Used in {@link BuildingsManager#addBuilding(String)}
     */
    public Building() {
        rooms = new MyArray<Room>(new Room[0]);
    }

    /**
     * Adds new room with provided parameters if name is valid (unique, not
     * empty)<br>
     * 
     * @param newRoomName
     *            - String (not empty, unique in the scope of building)
     * @param type
     *            - {@link RoomType}
     * @param capacity
     *            integer number not less than zero
     * @throws UniversityWarning
     *             <li>if provided newRoomName not unique, null or empty <li>if
     *             capacity < 0
     */
    public void addRoom(String newRoomName, Room.RoomType type, int capacity) throws UniversityWarning {
        Room newRoom = new Room();
        newRoom.setBuilding(this);
        newRoom.setName(newRoomName);
        newRoom.setCapacity(capacity);
        newRoom.setToOther();
        if (RoomType.AUDITORIUM.equals(type))
            newRoom.setToAuditorium();
        if (RoomType.LABORATORY.equals(type))
            newRoom.setToLaboratory();
        rooms.add(newRoom);
    }

    /**
     * Adds new default room if name is valid (unique, not empty)<br>
     * Calls {@link #addRoom(String, Room.RoomType, int)}
     * 
     * @param newRoomName
     *            not empty, not null String, unique within Building rooms
     *            collection.
     * @throws UniversityWarning
     *             if provided newRoomName not unique, null or empty
     */
    public void addRoom(String newRoomName) throws UniversityWarning {
        addRoom(newRoomName, RoomType.OTHER, 0);
    }

    /**
     * @return rooms count in building
     */
    public int countRooms() {
        if (rooms == null) {
            return 0;
        }
        return rooms.size();
    }

    /**
     * Returns name of the building. Used in {@link MyArrayToNamesCovertable}
     * mechanics
     */
    @Override
    public String getName() {
        return name;
    }

    /**
     * Return instance of Room stored under index.<br>
     * There is safer way to get rooms via {@link #getRooms()}
     * 
     * @param index
     *            - valid index
     * @return {@link Room} under the index
     * @throws UniversityFailure
     *             - in case of crash
     * @throws UniversityWarning
     *             - if index is not correct
     */
    public Room getRoomByIndex(int index) throws UniversityFailure, UniversityWarning {
        if (index < 0 || countRooms() <= index) {
            throw new UniversityWarning(ErrorCodes.ROOM_NOT_FOUND_BY_INDEX + index);
        }
        return rooms.get(index);
    }

    /**
     * Returns copy of rooms collection Safer than {@link #getRoomByIndex(int)}
     * as there are no thrown exceptions
     * 
     * @return copy() of rooms collection as MyArray&lt;Room&gt;
     */
    public MyArray<Room> getRooms() {
        return rooms.copy();
    }

    /**
     * Throws {@link UniversityWarning}(ErrorCodes.ROOM_NOT_UNIQUE + newName)
     * when room name is already exists in the rooms collection<br>
     * Used for unique name check in {@link Room#setName(String)}
     * 
     * @param newName
     *            - name to check
     * @throws UniversityWarning
     *             when room with equal name found
     */
    void hasDuplicates(String newName) throws UniversityWarning {
        for (Room r : rooms) {
            if (r.getName().equals(newName)) {
                throw new UniversityWarning(ErrorCodes.ROOM_NOT_UNIQUE + newName);
            }
        }
    }

    /**
     * Removes room from collections and clears all links to room using
     * {@link Room#removeLinks()}
     * 
     * @param r
     *            - Room to remove
     * @throws UniversityFailure
     *             when application crashes
     * @throws UniversityWarning
     *             when room could not be removed (not in collection or some
     *             links could not be removed)
     */
    public void removeRoom(Room r) throws UniversityWarning {
        r.removeLinks();
        rooms.remove(r);
    }

    /**
     * Sets {@link BuildingsManager} for the building. Used on adding new
     * Building<br>
     * BuildingManager is used as a scope for University building and name
     * unique check
     * 
     * @param bm
     *            - BuildingManager of the university. Typically
     *            {@link University#buildings university.buildings}
     * @throws UniversityWarning
     *             when manager is already set or null provided as a manager
     */
    public void setManager(BuildingsManager bm) throws UniversityWarning {
        if (bm != null && manager == null) {
            manager = bm;
        } else {
            throw new UniversityWarning(ErrorCodes.BUILDING_NOT_REASSIGNED);
        }

    }

    /**
     * Sets name of the building if not empty String provided<br>
     * If manager is set by {@link #setManager(BuildingsManager)} - verifies
     * that name is unique in the scope of defined {@link BuildingsManager}
     * using {@link #validateNameUnique(String)}
     * 
     * @param newName
     *            - not empty String
     * @throws UniversityWarning
     *             when empty
     */
    public void setName(String newName) throws UniversityWarning {
        if (newName == null || Const.STRING_EMPTY.equals(newName)) {
            throw new UniversityWarning(ErrorCodes.BUILDING_NAME_NULL);
        }
        if (!newName.equals(getName())) {
            validateNameUnique(newName);
            this.name = newName;
        }
    }

    /**
     * Checks that name is unique in the scope of defined
     * {@link BuildingsManager manager}<br>
     * Does nothing if manager is not set
     * 
     * @param newName
     *            - String to check
     * @throws UniversityWarning
     *             when name is not unique
     */
    private void validateNameUnique(String newName) throws UniversityWarning {
        if (manager != null)
            manager.hasDuplicates(newName);
    }

    /**
     * Removes all links from the building and included rooms using (calls
     * {@link Room#removeLinks()})<br>
     * Clears {@link BuildingsManager manager} link<br>
     * Called from {@link BuildingsManager manager} before
     * {@link BuildingsManager#removeBuilding(Building)
     * removeBuilding(Building)}
     * 
     * @throws UniversityWarning
     *             when some links could not be removed (no cases yet)
     * @throws UniversityFailure
     *             when application crashes
     * 
     */
    protected void removeLinks() throws UniversityWarning {
        for (Room r : getRooms().copy()) {
            if (r != null)
                r.removeLinks();
            removeRoom(r);
        }
        manager = null;
    }
}
